// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/loader/async_resource_handler.h"

#include <memory>
#include <stddef.h>
#include <stdint.h>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/process/process.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/loader/resource_loader.h"
#include "content/browser/loader/resource_loader_delegate.h"
#include "content/browser/loader/resource_message_filter.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/common/resource_messages.h"
#include "content/common/resource_request.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/common/content_features.h"
#include "content/public/common/previews_state.h"
#include "content/public/common/resource_type.h"
#include "content/public/test/mock_resource_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_message_macros.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/ssl/client_cert_store.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_job.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_job.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"

namespace content {

namespace {

    std::string GenerateHeader(size_t response_data_size)
    {
        return base::StringPrintf(
            "HTTP/1.1 200 OK\n"
            "Content-type: text/html\n"
            "Content-Length: %" PRIuS "\n",
            response_data_size);
    }

    int64_t TotalReceivedBytes(size_t response_data_size)
    {
        return response_data_size + GenerateHeader(response_data_size).size();
    }

    std::string GenerateData(size_t response_data_size)
    {
        return std::string(response_data_size, 'a');
    }

    class TestProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler {
    public:
        TestProtocolHandler(size_t response_data_size)
            : response_data_size_(response_data_size)
        {
        }

        net::URLRequestJob* MaybeCreateJob(
            net::URLRequest* request,
            net::NetworkDelegate* network_delegate) const override
        {
            return new net::URLRequestTestJob(request, network_delegate,
                GenerateHeader(response_data_size_),
                GenerateData(response_data_size_), true);
        }

    private:
        size_t response_data_size_;
    };

    // A subclass of ResourceMessageFilter that records IPC messages that are sent.
    class RecordingResourceMessageFilter : public ResourceMessageFilter {
    public:
        RecordingResourceMessageFilter(ResourceContext* resource_context,
            net::URLRequestContext* request_context)
            : ResourceMessageFilter(
                0,
                nullptr,
                nullptr,
                nullptr,
                nullptr,
                base::Bind(&RecordingResourceMessageFilter::GetContexts,
                    base::Unretained(this)))
            , resource_context_(resource_context)
            , request_context_(request_context)
        {
            InitializeForTest();
            set_peer_process_for_testing(base::Process::Current());
        }

        const std::vector<std::unique_ptr<IPC::Message>>& messages() const
        {
            return messages_;
        }

        // IPC::Sender implementation:
        bool Send(IPC::Message* message) override
        {
            // Unpickle the base::SharedMemoryHandle to avoid warnings about
            // "MessageAttachmentSet destroyed with unconsumed descriptors".
            if (message->type() == ResourceMsg_SetDataBuffer::ID) {
                ResourceMsg_SetDataBuffer::Param params;
                ResourceMsg_SetDataBuffer::Read(message, &params);
            }
            messages_.push_back(base::WrapUnique(message));
            return true;
        }

    private:
        ~RecordingResourceMessageFilter() override { }

        void GetContexts(ResourceType resource_type,
            ResourceContext** resource_context,
            net::URLRequestContext** request_context)
        {
            *resource_context = resource_context_;
            *request_context = request_context_;
        }

        ResourceContext* const resource_context_;
        net::URLRequestContext* const request_context_;
        std::vector<std::unique_ptr<IPC::Message>> messages_;
    };

    class AsyncResourceHandlerTest : public ::testing::Test,
                                     public ResourceLoaderDelegate {
    protected:
        AsyncResourceHandlerTest()
            : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP)
            , context_(true)
        {
        }

        void TearDown() override
        {
            if (filter_)
                filter_->OnChannelClosing();
            // Prevent memory leaks.
            filter_ = nullptr;
            rdh_.Shutdown();
            base::RunLoop().RunUntilIdle();
        }

        void CreateRequestWithResponseDataSize(size_t response_data_size)
        {
            test_job_factory_.SetProtocolHandler(
                "test", base::MakeUnique<TestProtocolHandler>(response_data_size));
            context_.set_job_factory(&test_job_factory_);
            context_.Init();
            std::unique_ptr<net::URLRequest> request = context_.CreateRequest(
                GURL("test:test"), net::DEFAULT_PRIORITY, nullptr);
            resource_context_ = base::MakeUnique<MockResourceContext>(&context_);
            filter_ = new RecordingResourceMessageFilter(resource_context_.get(), &context_);
            ResourceRequestInfoImpl* info = new ResourceRequestInfoImpl(
                filter_->requester_info_for_test(),
                0, // route_id
                -1, // frame_tree_node_id
                0, // origin_pid
                0, // request_id
                0, // render_frame_id
                false, // is_main_frame
                false, // parent_is_main_frame
                RESOURCE_TYPE_IMAGE, // resource_type
                ui::PAGE_TRANSITION_LINK, // transition_type
                false, // should_replace_current_entry
                false, // is_download
                false, // is_stream
                false, // allow_download
                false, // has_user_gesture
                false, // enable load timing
                false, // enable upload progress
                false, // do_not_prompt_for_login
                blink::WebReferrerPolicyDefault, // referrer_policy
                blink::WebPageVisibilityStateVisible, // visibility_state
                resource_context_.get(), // context
                false, // report_raw_headers
                true, // is_async
                PREVIEWS_OFF, // previews_state
                std::string(), // original_headers
                nullptr, // body
                false); // initiated_in_secure_context
            info->AssociateWithRequest(request.get());
            std::unique_ptr<AsyncResourceHandler> handler = base::MakeUnique<AsyncResourceHandler>(request.get(), &rdh_);
            loader_ = base::MakeUnique<ResourceLoader>(
                std::move(request), std::move(handler), this);
        }

        void StartRequestAndWaitWithResponseDataSize(size_t response_data_size)
        {
            CreateRequestWithResponseDataSize(response_data_size);
            loader_->StartRequest();
            finish_waiter_.reset(new base::RunLoop);
            finish_waiter_->Run();
        }

        scoped_refptr<RecordingResourceMessageFilter> filter_;

    private:
        // ResourceLoaderDelegate implementation:
        ResourceDispatcherHostLoginDelegate* CreateLoginDelegate(
            ResourceLoader* loader,
            net::AuthChallengeInfo* auth_info) override
        {
            return nullptr;
        }

        bool HandleExternalProtocol(ResourceLoader* loader,
            const GURL& url) override
        {
            return false;
        }
        void DidStartRequest(ResourceLoader* loader) override { }
        void DidReceiveRedirect(ResourceLoader* loader,
            const GURL& new_url,
            ResourceResponse* response) override { }
        void DidReceiveResponse(ResourceLoader* loader) override { }
        void DidFinishLoading(ResourceLoader* loader) override
        {
            loader_.reset();
            finish_waiter_->Quit();
        }
        std::unique_ptr<net::ClientCertStore> CreateClientCertStore(
            ResourceLoader* loader) override
        {
            return nullptr;
        }

        TestBrowserThreadBundle thread_bundle_;
        ResourceDispatcherHostImpl rdh_;
        net::TestURLRequestContext context_;
        net::URLRequestJobFactoryImpl test_job_factory_;
        std::unique_ptr<MockResourceContext> resource_context_;
        std::unique_ptr<ResourceLoader> loader_;
        std::unique_ptr<base::RunLoop> finish_waiter_;
    };

    TEST_F(AsyncResourceHandlerTest, Construct)
    {
        CreateRequestWithResponseDataSize(1);
    }

    TEST_F(AsyncResourceHandlerTest, OneChunkLengths)
    {
        // Larger than kInlinedLeadingChunkSize and smaller than
        // kMaxAllocationSize.
        constexpr auto kDataSize = 4096;
        StartRequestAndWaitWithResponseDataSize(kDataSize);
        const auto& messages = filter_->messages();
        ASSERT_EQ(4u, messages.size());
        ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[2]->type());
        ResourceMsg_DataReceived::Param params;
        ResourceMsg_DataReceived::Read(messages[2].get(), &params);

        int encoded_data_length = std::get<3>(params);
        EXPECT_EQ(kDataSize, encoded_data_length);

        ASSERT_EQ(ResourceMsg_RequestComplete::ID, messages[3]->type());
        ResourceMsg_RequestComplete::Param completion_params;
        ResourceMsg_RequestComplete::Read(messages[3].get(), &completion_params);
        ResourceRequestCompletionStatus completion_status = std::get<1>(completion_params);

        EXPECT_EQ(TotalReceivedBytes(kDataSize),
            completion_status.encoded_data_length);
        EXPECT_EQ(kDataSize, completion_status.encoded_body_length);
    }

    TEST_F(AsyncResourceHandlerTest, InlinedChunkLengths)
    {
        // TODO(ricea): Remove this Feature-enabling code once the feature is on by
        // default.
        base::test::ScopedFeatureList scoped_feature_list;
        scoped_feature_list.InitAndEnableFeature(
            features::kOptimizeLoadingIPCForSmallResources);

        // Smaller than kInlinedLeadingChunkSize.
        constexpr auto kDataSize = 8;
        StartRequestAndWaitWithResponseDataSize(kDataSize);
        const auto& messages = filter_->messages();
        ASSERT_EQ(3u, messages.size());
        ASSERT_EQ(ResourceMsg_InlinedDataChunkReceived::ID, messages[1]->type());
        ResourceMsg_InlinedDataChunkReceived::Param params;
        ResourceMsg_InlinedDataChunkReceived::Read(messages[1].get(), &params);

        int encoded_data_length = std::get<2>(params);
        EXPECT_EQ(kDataSize, encoded_data_length);

        ASSERT_EQ(ResourceMsg_RequestComplete::ID, messages[2]->type());
        ResourceMsg_RequestComplete::Param completion_params;
        ResourceMsg_RequestComplete::Read(messages[2].get(), &completion_params);
        ResourceRequestCompletionStatus completion_status = std::get<1>(completion_params);

        EXPECT_EQ(TotalReceivedBytes(kDataSize),
            completion_status.encoded_data_length);
        EXPECT_EQ(kDataSize, completion_status.encoded_body_length);
    }

    TEST_F(AsyncResourceHandlerTest, TwoChunksLengths)
    {
        // Larger than kMaxAllocationSize.
        constexpr auto kDataSize = 64 * 1024;
        StartRequestAndWaitWithResponseDataSize(kDataSize);
        const auto& messages = filter_->messages();
        ASSERT_EQ(5u, messages.size());
        ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[2]->type());
        ResourceMsg_DataReceived::Param params;
        ResourceMsg_DataReceived::Read(messages[2].get(), &params);

        int encoded_data_length = std::get<3>(params);
        EXPECT_EQ(32768, encoded_data_length);

        ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[3]->type());
        ResourceMsg_DataReceived::Read(messages[3].get(), &params);

        encoded_data_length = std::get<3>(params);
        EXPECT_EQ(32768, encoded_data_length);

        ASSERT_EQ(ResourceMsg_RequestComplete::ID, messages[4]->type());
        ResourceMsg_RequestComplete::Param completion_params;
        ResourceMsg_RequestComplete::Read(messages[4].get(), &completion_params);
        ResourceRequestCompletionStatus completion_status = std::get<1>(completion_params);
        EXPECT_EQ(TotalReceivedBytes(kDataSize),
            completion_status.encoded_data_length);
        EXPECT_EQ(kDataSize, completion_status.encoded_body_length);
    }

} // namespace

} // namespace content
